package com.store.orm.mongo.curd;

import com.store.orm.mongo.annotation.CollectionField;
import com.store.orm.mongo.annotation.Documents;
import com.store.orm.mongo.annotation.Id;
import com.store.orm.util.JsonUtil;
import com.store.orm.util.StringUtil;
import org.bson.Document;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Objects;

/**
 * 提供了丰富的对象转换方法，包括：
 * <p>
 * 将对象解析为 LinkedHashMap
 * 将 LinkedHashMap 解析为一个 Document
 * 将一个 Document 解析为一个对象
 * 将一个 LinkedHashMap 解析为一个级联 LinkedHashMap
 * </p>
 * 使用建议:
 * 先使用 parseT 把一个对象解析成为一个 LinkedHashMap
 * 在使用 parseMap 把一个 LinkedHashMap 解析成为 Document
 * 以便于在新增数据时使用
 *
 * @author 宫静雨
 * @version 1.0
 */
@SuppressWarnings("all")
public class MongoUtil {

    /**
     * 将一个对象转换为 Document 对象
     */
    public <T> Document parseTToDoc(T t) throws Exception {
        Class<?> cls = t.getClass();
        Field[] fields = cls.getDeclaredFields();
        Document document = new Document();
        for (Field field : fields) {
            field.setAccessible(true);
            Object value = field.get(t);
            if (Objects.nonNull(value)) {
                String key = fieldName(field);
                String name = field.getType().getName();
                if (name.startsWith("java.") || name.startsWith("org.bson.types.")) {
                    if (name.contains("List") || name.contains("ArrayList")) {
                        List list = (List) value;
                        List res = new ArrayList();
                        list.forEach(ac -> {
                            try {
                                res.add(parseTToDoc(ac));
                            } catch (Exception e) {
                                e.printStackTrace();
                            }
                        });
                        document.append(key, res);
                    } else {
                        document.append(key, value);
                    }
                } else {
                    document.append(key, parseTToDoc(value));
                }
            }
        }
        return document;
    }

    /**
     * 将对象解析，放入 Map 中, 以便于涉及对象方法的使用
     * 对象内部可以嵌套对象
     *
     * @param t   将要被解析的实体类
     * @param <T> 实体类的类型
     * @return 返回一个 LinkedHashMap
     */
    public <T> LinkedHashMap<String, Object> parseTToMap(T t) throws Exception {
        Class<?> cls = t.getClass();
        LinkedHashMap<String, Object> map = new LinkedHashMap<>();
        Field[] fields = cls.getDeclaredFields();
        for (Field field : fields) {
            field.setAccessible(true);
            Object value = field.get(t);
            if (Objects.nonNull(value)) {
                String key = fieldName(field);
                String fieldType = field.getType().getName();
                if (fieldType.startsWith("java") || fieldType.startsWith("org.bson.types")) {
                    if (fieldType.contains("List") || fieldType.contains("ArrayList")) {
                        List list = (List) value;
                        List res = new ArrayList();
                        list.forEach(ac -> {
                            try {
                                res.add(parseTToDoc(ac));
                            } catch (Exception e) {
                                e.printStackTrace();
                            }
                        });
                        map.put(key, res);
                    } else {
                        map.put(key, value);
                    }
                } else {
                    LinkedHashMap<String, Object> subMap = parseTToMap(value);
                    map.put(key, subMap);
                }
            }
        }
        return map;
    }

    /**
     * 可以把一个级联 map 转化成为一个单级 map，有助于过滤条件的封装
     * 参考测试代码: com.store.orm.mongo.crud.SelectTest.testParse
     * {a=a,b=b,c={c1=c1, c2=c2},D={D1=d1,D2={d22=d22, d333={dac=dds}}}}
     * {a=a, b=b, c.c1=c1, c.c2=c2, D.D1=d1, D.D2.d333.dac=dds, D.D2.d22=d22}
     *
     * @param map 级联 map
     * @return 单级 map
     */
    public LinkedHashMap<String, Object> cascade(LinkedHashMap<String, Object> map) {
        LinkedHashMap<String, Object> res = new LinkedHashMap<>();
        map.forEach((k, v) -> {
            if (v instanceof LinkedHashMap) {
                LinkedHashMap<String, Object> linkedHashMap = (LinkedHashMap) v;
                cascade(linkedHashMap).forEach((k2, v2) -> res.put(k + "." + k2, v2));
            } else {
                res.put(k, v);
            }
        });
        return res;
    }

    /**
     * 把一个 LinkedHashMap 的内容放到 Document 中
     *
     * @param map 将要被解析的 LinkedHashMap
     * @return 返回一个 Document
     */
    public Document parseMapToDocument(LinkedHashMap<String, Object> map) {
        Document document = new Document();
        map.forEach((k, v) -> {
            if (v instanceof LinkedHashMap) {
                Document subDoc = parseMapToDocument(((LinkedHashMap<String, Object>) v));
                document.append(k, subDoc);
            } else if (v instanceof List) {
                List list = (List) v;
                if (list.size() != 0 && !list.get(0).getClass().getTypeName().startsWith("java.")) {
                    List res = new ArrayList();
                    list.forEach(val -> {
                        try {
                            res.add(parseTToDoc(val));
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                    });
                    document.append(k, res);
                } else {
                    document.append(k, list);
                }
            } else {
                if (k.equals("id")) {
                    document.append("_id", v);
                } else {
                    document.append(k, v);
                }
            }
        });
        return document;
    }

    /**
     * 获取字段的名字
     */
    private String fieldName(Field field) {
        String fieldName;
        if (field.isAnnotationPresent(CollectionField.class)) {
            CollectionField collectionField = field.getAnnotation(CollectionField.class);
            fieldName = collectionField.value();
        } else if (field.isAnnotationPresent(Id.class)) {
            fieldName = "_id";
        } else {
            fieldName = field.getName();
        }
        return fieldName;
    }

    /**
     * 根据 cls 获取集合的名称
     *
     * @param cls 实体类
     * @return 集合的名称
     * @see Documents
     */
    public String collection(Class<?> cls) {
        String collection = "";
        if (cls.isAnnotationPresent(Documents.class)) {
            Documents documents = cls.getAnnotation(Documents.class);
            collection = documents.value();
        } else {
            collection = StringUtil.firstLower(cls.getSimpleName());
        }
        return collection;
    }

    /**
     * 将一个 JSON 字符串解析为 Document 对象
     *
     * @param json 将要被解析的字符串
     */
    public <T> Document parseJsonToDoc(String json) throws Exception {
        LinkedHashMap<String, Object> parse = JsonUtil.parse(json);
        return parseMapToDocument(parse);
    }

    /**
     * 将一个 Document 转换为 Class 类型的对象
     *
     * @param document 从 mongodb 中获取的对象
     * @param cls      Java 实体类的类型
     * @return 返回一个 T 类型的对象
     */
    public <T> T parseDocToPojo(Document document, Class<T> cls) {
        Field[] fields = cls.getDeclaredFields();
        T t = null;
        try {
            t = cls.newInstance();
            for (Field field : fields) {
                field.setAccessible(true);
                String key = fieldName(field);
                Object o = document.get(key);
                if (Objects.nonNull(o)) {
                    String type = field.getType().getName();
                    if (type.startsWith("java.") || type.startsWith("org.bson.types.")) {
                        if (type.contains("List") || type.contains("ArrayList")) {
                            ArrayList value = (ArrayList) o;
                            String generic = field.getGenericType().getTypeName().split("<")[1].split(">")[0];
                            Class<?> genericClass = Class.forName(generic);
                            List res = new ArrayList();
                            value.forEach(doc -> {
                                res.add(parseDocToPojo((Document) doc, genericClass));
                            });
                            field.set(t, res);
                        } else {
                            field.set(t, o);
                        }
                    } else {
                        Document doc = (Document) o;
                        Object subO = parseDocToPojo(doc, field.getType());
                        field.set(t, subO);
                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return t;
    }
}